package com.fyf.game.net.client;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import org.je.log.JLog;
import com.fyf.game.net.DataAnalyzer;
import com.fyf.game.net.GameProtocol;
import com.fyf.game.net.NioUtil;
import com.fyf.game.net.ReceiveHandler;
import com.fyf.game.net.SelectManager;
import com.fyf.game.net.Sender;
import com.fyf.game.net.SenderBox;

/**
 * @author fanyifeng
 */
public class MessageChannel implements Sender
{
	private String					ip;
	private int						port;
	private final SelectManager		selMgr;

	private SelectionKey			usedKey;
	private final ByteBuffer		readTemp;
	private final DataAnalyzer		analyzer;
	private final List<ByteBuffer>	writeDatas;

	private boolean					closeFlag;
	private boolean					isConnected;
	private boolean					isStart;
	private boolean					closeOver;

	public MessageChannel(ReceiveHandler handler) {
		isStart = false;
		closeFlag = false;
		closeOver = false;
		isConnected = false;
		selMgr = new SelectManager();
		readTemp = ByteBuffer.allocate(1024);
		writeDatas = new LinkedList<ByteBuffer>();
		analyzer = new DataAnalyzer(handler, this, new AtomicLong(0));
	}

	private void doNetWork() //
	{
		while (closeFlag == false) //
		{
			selMgr.setNeedWakeup();

			synchronized(this) {}

			if (selMgr.select() <= 0)
				continue;

			Iterator<SelectionKey> it = selMgr.keys();

			while (it.hasNext()) {
				SelectionKey key = it.next();
				it.remove();

				if (!key.isValid()) {
					NioUtil.cancelAndClose(key);
					continue;
				}

				try {
					if (key.isReadable()) {
						doRead(key);
						continue;
					}
					if (key.isWritable()) {
						doWrite(key);
						continue;
					}
				} catch (Exception ex) {
					NioUtil.cancelAndClose(key);
					JLog.error(ex);
				}
				try {
					if (key.isConnectable()) {
						doConnectable(key);
					}
				} catch (Exception ex) {
					JLog.error(ex);
					ex.printStackTrace();
					this.closeFlag = true;
					break;
				}
			}
		}
		cancelAndClose();
		selMgr.close();
		closeOver = true;
	}

	public boolean isClosed() {
		return closeOver;
	}

	private void doConnectable(SelectionKey key) throws IOException {
		checkKey(key);
		SocketChannel channel = (SocketChannel) key.channel();
		if (channel.finishConnect() == false) {
			return;
		}
		synchronized(writeDatas) {
			isConnected = true;
			if (writeDatas.size() == 0) {
				key.interestOps(SelectionKey.OP_READ);
			} else {
				key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
			}
		}
	}

	private void doRead(SelectionKey key) throws IOException {
		checkKey(key);
		SocketChannel channel = (SocketChannel) key.channel();
		readTemp.clear();
		if (channel.read(readTemp) < 0) {// 异常退出
			JLog.error("读取到了-1，服务器异常结束连接.");
			cancelAndClose();
			return;
		}
		analyzer.saveAndAnalyse(readTemp);
		if (analyzer.needClose()) {// 正常退出
			cancelAndClose();
			closeFlag = true;
		}
	}

	private void doWrite(SelectionKey key) throws IOException {
		checkKey(key);
		SocketChannel channel = (SocketChannel) key.channel();
		ByteBuffer wb = null;

		synchronized(writeDatas) {
			if (writeDatas.size() == 0) {
				usedKey.interestOps(SelectionKey.OP_READ);
				return;
			}
			wb = writeDatas.get(0);
		}

		channel.write(wb);
		if (wb.hasRemaining())//
			return;

		synchronized(writeDatas) {
			writeDatas.remove(0);
			if (writeDatas.size() == 0) {
				usedKey.interestOps(SelectionKey.OP_READ);
			}
		}
	}

	public void send(String message) {
		ByteBuffer bf = GameProtocol.wrap(message);
		synchronized(writeDatas) {
			writeDatas.add(bf);
			if (isConnected) {
				usedKey.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
				selMgr.wakeup();
			}
		}
	}

	public void outLine() {
		send("");
	}

	/**
	 * @param ip
	 * @param port
	 */
	public void connect(String ip, int port) {
		synchronized(this) {
			if (isStart) {
				throw new RuntimeException("this-channel-is-started.");
			}
			isStart = true;
		}

		new Thread() {
			public void run() {
				MessageChannel.this.doNetWork();
			}
		}.start();

		this.ip = ip;
		this.port = port;

		try {
			SocketChannel channel = NioUtil.createAsynChannel();
			synchronized(this) {
				selMgr.wakeup();
				usedKey = selMgr.register(channel, SelectionKey.OP_CONNECT);
			}
			channel.connect(new InetSocketAddress(ip, port));
		} catch (Exception ex) {
			JLog.error(ex);
			cancelAndClose();
		}
	}

	private void cancelAndClose() {
		if (usedKey == null)
			return;
		closeFlag = true;
		NioUtil.cancelAndClose(usedKey);
		usedKey = null;
	}

	private void checkKey(SelectionKey key) {
		if (key != usedKey) {
			cancelAndClose();
			usedKey = key;
		}
	}

	public void close() {
		this.closeFlag = true;
		selMgr.wakeup();
	}

	public String getIp() {
		return ip;
	}

	public int getPort() {
		return port;
	}

	public String getID() {
		return null;
	}

	public SenderBox getBox() {
		return null;
	}

	public void distroy() {}

	public String getName() {
		return null;
	}
}
